package org.mathig.poker;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.mathig.poker.hand.Flush;
import org.mathig.poker.hand.FourOfAKind;
import org.mathig.poker.hand.FullHouse;
import org.mathig.poker.hand.Hand;
import org.mathig.poker.hand.HighCard;
import org.mathig.poker.hand.Pair;
import org.mathig.poker.hand.Straight;
import org.mathig.poker.hand.StraightFlush;
import org.mathig.poker.hand.ThreeOfAKind;
import org.mathig.poker.hand.TwoPair;
import org.mathig.poker.util.MapOfList;

/**
 * A Poker Hand of 5 cards.
 * 
 * @author mathig
 */
public class PokerHand implements Comparable<PokerHand> {

	private List<Card> cards;

	private PokerRank rank;

	@Override
	public String toString() {
		return rank.toString();
	}

	public PokerHand(List<Card> fiveCards) {
		if (fiveCards.size() != 5)
			throw new IllegalArgumentException("must be 5 cards !");
		this.cards = fiveCards;
	}

	public static Hand searchBestHand(List<Card> sevenCards) {
		if (sevenCards.size() != 7)
			throw new IllegalArgumentException("must be 7 cards !");

		Collections.sort(sevenCards);
		Collections.reverse(sevenCards);

		MapOfList<Rank, Card> byRank = new MapOfList<Rank, Card>();
		MapOfList<Color, Card> byColor = new MapOfList<Color, Card>();
		for (Card card : sevenCards) {
			byRank.add(card.getRank(), card);
			byColor.add(card.getColor(), card);
		}

		// for many of a kind
		List<List<Card>> rankCards = byRank.valuesBySizeDesc();
		List<Card> rankCards0 = rankCards.get(0);
		List<Card> rankCards1 = rankCards.get(1);

		// for flush
		List<List<Card>> colorCards = byColor.valuesBySizeDesc();
		List<Card> colorCards0 = colorCards.get(0);

		// search suit of same color
		if (colorCards0.size() >= 5) {
			List<Card> straightFlush = searchStraight(colorCards0);
			if (straightFlush != null)
				return new StraightFlush(straightFlush);
		}

		// quad
		if (rankCards0.size() == 4) {
			sevenCards.removeAll(rankCards0);
			return new FourOfAKind(rankCards0, sevenCards.get(0));
		}

		// full
		if (rankCards0.size() == 3 && rankCards1.size() >= 2) {
			return new FullHouse(rankCards0, rankCards1.subList(0, 2));
		}

		// flush
		if (colorCards0.size() >= 5) {
			return new Flush(colorCards0.subList(0, 5));
		}

		// straight
		List<Card> straight = searchStraight(sevenCards);
		if (straight != null) {
			return new Straight(straight);
		}

		// three of a kind
		if (rankCards0.size() == 3) {
			sevenCards.removeAll(rankCards0);
			return new ThreeOfAKind(rankCards0, sevenCards.subList(0, 2));
		}

		// two pair
		if (rankCards0.size() == 2 && rankCards1.size() == 2) {
			sevenCards.removeAll(rankCards0);
			sevenCards.removeAll(rankCards1);
			return new TwoPair(rankCards0, rankCards1, sevenCards.get(0));
		}

		// pair
		if (rankCards0.size() == 2) {
			sevenCards.removeAll(rankCards0);
			return new Pair(rankCards0, sevenCards.subList(0, 3));
		}

		// high card
		Card highCard = sevenCards.remove(0);
		return new HighCard(highCard, sevenCards.subList(0, 4));
	}

	private static List<Card> searchStraight(List<Card> cards) {
		List<Card> suit = new ArrayList<Card>();
		for (Card card : cards) {
			if (suit.isEmpty() || suit.get(suit.size() - 1).getRank().ordinal() == card.getRank().ordinal() + 1) {
				suit.add(card);
				if (suit.size() == 5)
					return suit;
			} else {
				suit.clear();
				suit.add(card);
			}
		}
		return null;
	}

	public int compareTo(PokerHand ph) {
		return rank.compareTo(ph.rank);
	}

	public List<Card> getCards() {
		return cards;
	}

	public void setCards(List<Card> cards) {
		this.cards = cards;
	}

	public PokerRank getRank() {
		return rank;
	}

	public void setRank(PokerRank rank) {
		this.rank = rank;
	}
}
